//*************************************************************************************
// File:        SimulaTimer.cs
//*************************************************************************************
// Description: Implements a virtual time clock intended for frame calculations
//              by the class Xeralabs.GDESK.Utils.Internals.Scheduler
//*************************************************************************************
// Classes:     SimulaTimer
//*************************************************************************************
// Author:      ManOguaR
//*************************************************************************************

using System;

namespace Xeralabs.Core.Utils.Timing
{
    /// <summary>
    /// Implements a virtual time clock intended for frame calculations by the class <see cref="Xeralabs.Core.Utils.Internals.Scheduler"/>
    /// </summary>
    public class VitualClock : IDisposable 
    {
        private bool _running;

        private double _thisTime;
        private double _lastTime;

        private double _systemTime;
        private double _systemOffset;
        private double _pauseTime;

        private ulong _frameCount;
        private double _frameStart;
        private double _frameEnd;

        private double _simTime;
        private double _simOffset;

        private HiResTimer _timer;

        private int _desiredFps;
        private double _msPerFrame;

        /// <summary>
        /// Constructor.
        /// </summary>
        public VitualClock()
            : this(-1) { }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="fps">Desired fps limit. -1 for no fps limitation.</param>
        public VitualClock(int fps)
        {
            _desiredFps = fps;
            if (_desiredFps > 0)
                _msPerFrame = 1000.0 / _desiredFps;

            _timer = new HiResTimer();
            _timer.Start();
            Reset();
        }

        /// <summary>
        /// Resets the clock to initial conditions.
        /// </summary>
        public void Reset()
        {	
            _running = false;

            _thisTime = _timer.GetTime();

            _lastTime = _thisTime;

            _systemTime = 0.0;
            _pauseTime = 0.0;
            _systemOffset = _thisTime;

            _frameCount = 0;
            _frameStart = 0.0;
            _frameEnd = 0.0;

            _simTime = 0.0;
            _simOffset = 0.0;
        }
        /// <summary>
        /// Returns the clock working state.
        /// </summary>
        /// <returns>True: clock is running; false: clock is stopped.</returns>
        public bool IsRunning() { return _running; }
        /// <summary>
        /// Method to start clock (or to restart it if stopped).
        /// </summary>
        public void Run()
        {
            if (!_running)
            {
                Update();
                _simOffset += (_systemTime - _pauseTime);
            }
            _running = true;
        }
        /// <summary>
        /// Method to stop (pause) the cirtual clock.
        /// </summary>
        public void Stop()
        {
            if (_running)
            {
                Update();
                _pauseTime = _systemTime;
            }
            _running = false;
        }

        /// <summary>
        /// Increments frame counter and samples real time to determine elapsed time since last frame.
        /// </summary>
        public double DoFrame()
        {	
            // <Render frame n>
            // <Begin frame n+1>
            // <Task1>
            // <Task2>
            // <Task3...>
            // <Render frame n+1>
            //
            _frameCount++;
            
            Update(); // update system time

            if (_running)
            {
                if (_desiredFps <= 0)
                {
                    _frameStart = _frameEnd;                // current frame begin = last frame end.
                    _frameEnd = _systemTime - _simOffset;   // current frame end = actual time.
                    _simTime = _frameStart;                 // simulation time is placed at frame beginning.
                }
                else
                {
                    _frameStart = _frameEnd;
                    _frameEnd += _msPerFrame;
                    _simTime = _frameStart;
                }
            }

            return _systemTime;
        }

        /// <summary>
        /// Upadtes and return system time.
        /// </summary>
        /// <returns>System time.</returns>
        public double GetSystem() { Update(); return _systemTime; }
        /// <summary>
        /// Returns simulation time.
        /// </summary>
        /// <returns>Simulation time.</returns>
        public double GetTime() { return _simTime; }
        /// <summary>
        /// Returns frame counter.
        /// </summary>
        /// <returns>Frame counter.</returns>
        public ulong GetFrameCount() { return _frameCount; }
        /// <summary>
        /// Returns current frame start time (also last frame end time).
        /// </summary>
        /// <returns>Current frame start time.</returns>
        public double GetFrameStart() { return _frameStart; }
        /// <summary>
        /// Returns current frame end time (start time, DoFrame()).
        /// </summary>
        /// <returns>Current frame end time.</returns>
        public double GetFrameEnd() { return _frameEnd; }
        /// <summary>
        /// Returns current frame elapsed time (FrameEnd - FrameStart).
        /// </summary>
        /// <returns>Current frame elapsed time.</returns>
        public double GetFrameElapsedTime() { return _frameEnd - _frameStart; }

        /// <summary>
        /// Advances clock to a new time value.
        /// </summary>
        /// <param name="newTime">New time value.</param>
        public void AdvanceTo(double newTime)
        {
            if (_running && newTime >= _simTime)
            {
                _simTime = newTime;
            }
        }
        /// <summary>
        /// Advances clock to frame end.
        /// </summary>
        public void AdvanceToEnd()
        {
            if (_running)
            {
                _simTime = _frameEnd;
            }
        }

        private void Update()
        {
            double elapsed = 0.0;
            _lastTime = _thisTime;
            _thisTime = _timer.GetTime();

            // convetimos a tiempo transcurrido.
            if (_thisTime < _lastTime)
                elapsed = _lastTime - _thisTime;
            else
                elapsed = _thisTime - _lastTime;

            // system time es tiempo real y nunca se detiene.
            _systemTime += elapsed;
        }

        #region Miembros de IDisposable
        public void Dispose()
        {
            _timer.Stop(); // release win32 time resolution
            _timer = null;
        }
        #endregion
    }
}
